fix(gui): Stop game time clock from jittering horizontally - #2860
fix(gui): Stop game time clock from jittering horizontally#2860bobtista wants to merge 4 commits into
Conversation
|
| Filename | Overview |
|---|---|
| GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | Adds width measurement for digits, colon, and dot in refreshGameTimeResources(), stores reserved widths, and uses them in drawGameTime() to anchor the timer's left edge; frame counter is placed at the live right edge of the timer text. |
| GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h | Adds m_gameTimeReservedWidth and m_gameTimeFrameReservedWidth Int member fields with consistent column alignment matching surrounding code. |
| Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp | Identical fix replicated from GeneralsMD; same logic for measuring reserved widths and anchoring the timer's left edge in drawGameTime(). |
| Generals/Code/GameEngine/Include/GameClient/InGameUI.h | Adds the same two reserved-width member fields as the GeneralsMD header, maintaining existing alignment style. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["refreshGameTimeResources()\ncalled on font change"] --> B["Measure widest digit width\n(iterate '0'–'9' via getWidth)"]
B --> C["Measure colon width\nand dot width"]
C --> D["Store m_gameTimeReservedWidth\n= 6×maxDigitWidth + 2×colonWidth"]
C --> E["Store m_gameTimeFrameReservedWidth\n= dotWidth + 2×maxDigitWidth"]
D --> F["drawGameTime()\ncalled every frame"]
E --> F
F --> G["Set timer text: HH:MM:SS\nSet frame text: .NN"]
G --> H["Compute horizontalTimerOffset\n= screenW − posX − reservedTotal\n(STABLE — no live measurement)"]
H --> I["Compute horizontalFrameOffset\n= timerOffset + liveTimerWidth\n(frame butts against timer text)"]
I --> J["Draw timer at timerOffset\nDraw frame at frameOffset"]
Reviews (2): Last reviewed commit: "fix(gui): Stop game time clock from jitt..." | Re-trigger Greptile
| m_gameTimeString->setFont(gameTimeFont); | ||
| m_gameTimeFrameString->setFont(gameTimeFont); | ||
|
|
||
| // TheSuperHackers @fix bobtista 07/07/2026 Reserve widths from the widest digit so the clock does not jitter |
There was a problem hiding this comment.
We can streamline refreshGameTimeResources() by passing a 2-char stack buffer (WideChar buf[2] = {d, 0}) to setText() in the digit loop to avoid UnicodeString heap allocations.
There was a problem hiding this comment.
How? DisplayString::setText takes UnicodeString, so this will allocate.
xezon
left a comment
There was a problem hiding this comment.
This is really unfortunate complexity. Is there no other way? Which font character causes this?
| m_gameTimeString->setFont(gameTimeFont); | ||
| m_gameTimeFrameString->setFont(gameTimeFont); | ||
|
|
||
| // TheSuperHackers @fix bobtista 07/07/2026 Reserve widths from the widest digit so the clock does not jitter |
There was a problem hiding this comment.
TheSuperHackers @info because all this code is new from us.
|
|
||
| m_gameTimeString->setText(UnicodeString(L":")); | ||
| Int colonWidth = m_gameTimeString->getWidth(); | ||
| m_gameTimeString->setText(UnicodeString(L".")); |
| // TheSuperHackers @info this implicitly offsets the game timer from the right instead of left of the screen | ||
| int horizontalTimerOffset = TheDisplay->getWidth() - (Int)m_gameTimePosition.x - m_gameTimeString->getWidth() - m_gameTimeFrameString->getWidth(); | ||
| int horizontalFrameOffset = TheDisplay->getWidth() - (Int)m_gameTimePosition.x - m_gameTimeFrameString->getWidth(); | ||
| // TheSuperHackers @fix bobtista 07/07/2026 Anchor timer left to reserved widths so it stops jittering; butt frame against its live right edge |
| m_gameTimeString->setText(UnicodeString(L".")); | ||
| Int dotWidth = m_gameTimeString->getWidth(); | ||
|
|
||
| m_gameTimeReservedWidth = (6 * maxDigitWidth + 2 * colonWidth) + (dotWidth + 2 * maxDigitWidth); |
There was a problem hiding this comment.
Why is this necessary only for the game time on the right? Why not for the clock time on the left? That also uses numbers and colons, right?
The jitter essentially happens when the font numbers are not mono-sized. Font "Verdana" on Windows does have mono-sized font numbers.
| m_gameTimeString->setFont(gameTimeFont); | ||
| m_gameTimeFrameString->setFont(gameTimeFont); | ||
|
|
||
| // TheSuperHackers @fix bobtista 07/07/2026 Reserve widths from the widest digit so the clock does not jitter |
There was a problem hiding this comment.
How? DisplayString::setText takes UnicodeString, so this will allocate.
|
This issue does not affect Windows - I actually ended up finding a better fix that doesn't need to be upstream, closing this. GameTimeFont is INI-configurable, so a font without tabular digits would jitter in theory. If that’s ever worth guarding it’s a much smaller change than this PR |
Addresses #2859
drawGameTime() positioned the top-right clock by subtracting live-measured text width every frame, right-anchored to the screen edge. With the proportional Tahoma font, digit widths change as the time ticks (and the .NN frame counter changes every frame), so the whole timer shifted a pixel or two each frame.
Reserve a fixed width per field from the widest digit and anchor the timer's left edge to it so the white clock no longer moves as digits change. The frame counter is butted against the timer's live right edge so the two stay adjacent.
TODO: